Learnt a couple of things today. Not in depth, but now I am aware of these features and will explore them in depth someday.
I knew how to create custom objects in PowerShell and I have always tried to return output from my functions/ scripts as custom objects. I was also aware that you can set the default display properties so the output is neater.
Say I create a new object like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
# define a hash-table with the properties PS> $ObjProps = @{ name = "Rakhesh"; age = 34; sex = "Male"; location = "Oman" } # create a new custom object PS> $blahObj = New-Object -TypeName PSCustomObject -Property $ObjProps # output the object PS> $blahObj name age sex location ---- --- --- -------- Rakhesh 34 Male Oman |
Notice when I output the object all its properties are output. Usually I may not want that. I may want that only the name
property is output and the rest are silent, only shown if asked for.
It’s possible to define the default properties you are interested in. This link gives you more details, the tl;dr summary of which is as follows:
- All objects contain a member object called
PSStandardMembers
which defines the default properties of the object. - The
PSStandardMembers
object a member object calledDefaultDisplayPropertySet
. This object contains a property calledReferencedPropertyNames
which lists the default displayed properties of the object. - Apart from
DefaultDisplayPropertySet
you haveDefaultKeyPropertySet
andDefaultDisplayProperty
objects too. I am not sure whatDefaultDisplayProperty
does butDefaultKeyPropertySet
is used when sorting and grouping objects.
To set the PSStandardMembers
property of an object one does the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# define an array containing the properties you are interested in PS> $DefaultProps = @("Name", "Sex") # create a new object of type PSPropertySet using the previously created array # this object will be called DefaultDisplayPropertySet PS> $DefaultDisplay = New-Object System.Management.Automation.PSPropertySet("DefaultDisplayPropertySet",[string[]]$DefaultProps) # note: one could skip creating an array and specify the properties directly as an array above # create a PSStandardMembers object with the previously created DefaultDisplayPropertySet object PS> $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($DefaultDisplay) # add this object as a property to our object PS> $blahObj | Add-Member MemberSet PSStandardMembers $PSStandardMembers # output the object PS> $blahObj Name Sex ---- --- Rakhesh Male |
Notice now only the properties we specified are shown.
As an aside, and purely because I spent some time trying to figure this out, here’s how DefaultKeyPropertySet
influences sorting:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# create 5 custom objects PS> $Obj1 = New-Object -TypeName PSCustomObject -Property @{ Name = "Rakhesh"; Age = "34"; Sex = "Male" } PS> $Obj2 = New-Object -TypeName PSCustomObject -Property @{ Name = "Calvin"; Age = "4"; Sex = "Male" } PS> $Obj3 = New-Object -TypeName PSCustomObject -Property @{ Name = "Hobbes"; Age = "24"; Sex = "Male" } PS> $Obj4 = New-Object -TypeName PSCustomObject -Property @{ Name = "Lucy"; Age = "5"; Sex = "Female" } PS> $Obj5 = New-Object -TypeName PSCustomObject -Property @{ Name = "Sally"; Age = "45"; Sex = "Female" } # note that default sorting seems to be based on the last key ("sex") PS> $obj1,$obj2,$obj3,$obj4,$obj5 | sort Name Age Sex ---- --- --- Lucy 5 Female Sally 45 Female Hobbes 24 Male Rakhesh 34 Male Calvin 4 Male # I would like sorting to be based on "name", set it so PS> $DefaultKeys = @("Name") PS> $DefaultKeysObj = New-Object System.Management.Automation.PSPropertySet("DefaultKeyPropertySet",[string[]]$DefaultKeys) PS> $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($DefaultKeysObj) # add PSStandardMembers to all the objects PS> $Obj1 | Add-Member MemberSet PSStandardMembers $PSStandardMembers PS> $Obj2 | Add-Member MemberSet PSStandardMembers $PSStandardMembers PS> $Obj3 | Add-Member MemberSet PSStandardMembers $PSStandardMembers PS> $Obj4 | Add-Member MemberSet PSStandardMembers $PSStandardMembers PS> $Obj5 | Add-Member MemberSet PSStandardMembers $PSStandardMembers # note the sorting key has changed PS> $obj1,$obj2,$obj3,$obj4,$obj5 | sort Name Age Sex ---- --- --- Calvin 4 Male Hobbes 24 Male Lucy 5 Female Rakhesh 34 Male Sally 45 Female |
(Thanks to this post which made me realize what DefaultKeyPropertySet
does).
Back to DefaultDisplayPropertySet
– the problem is that it doesn’t work in PowerShell v2. It’s a bug with PowerShell v2 and this Stack Overflow post gives a workaround which involves creating a temporary ps1xml
file for the custom objects and defining its default properties.
I haven’t explored ps1xml
files much but the gist of the matter is (1) they are what PowerShell uses to format object output and (2) you can create custom ps1xml
files for your custom objects. The Stack Overflow post gives a function that takes an object and an array of properties and sets these properties as the default for that object. It’s a neat function and works as expected, but for a catch …
The catch is that since all custom objects have the same name you can’t set different default properties for different objects. Unless you give a name for the custom object, of course, which differentiates each type of custom object from the other. So how do you go about naming custom objects?
First up, how do you get the current name of an object? From my reflection post we know the following works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# creating a custom object (which will be used in the examples below) PS> $blah = New-Object -TypeName PSCustomObject # find its name PS> $blah.GetType().FullName System.Management.Automation.PSCustomObject # this too works, but gives you the shortname PS> $blah.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False PSCustomObject System.Object # this too ... PS> $blah | Get-Member TypeName: System.Management.Automation.PSCustomObject Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() |
To fiddle with the type name you have to use some hidden members of every object. (This was another new thing learnt today. Didn’t know objects had hidden members too). The way to see these is via Get-Member -Force
cmdlet. Have a look at the help for the -Force
parameter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
PS> help Get-Member -Parameter Force -Force [<SwitchParameter>] Adds the intrinsic members (PSBase, PSAdapted, PSObject, PSTypeNames) and the compiler-generated get_ and set_ methods to the display. By default, Get-Member gets these properties in all views other than "Base" and "Adapted," but it does not display them. The following list describes the properties that are added when you use the Force parameter: -- PSBase: The original properties of the .NET Framework object without extension or adaptation. These are the properties defined for the object class and listed in MSDN. -- PSAdapted: The properties and methods defined in the Windows PowerShell extended type system. -- PSExtended: The properties and methods that were added in the Types.ps1xml files or by using the Add-Member cmdlet. -- PSObject: The adapter that converts the base object to a Windows PowerShell PSObject object. -- PSTypeNames: A list of object types that describe the object, in order of specificity. When formatting the object, Windows PowerShell searches for the types in the Format.ps1xml files in the Windows PowerShell installation directory ($pshome). It uses the formatting definition for the first type that it finds. |
From the help file its clear PSTypeNames
is what we are interested in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# PSTypeNames gives you the class name of this object as well as its base class # If PowerShell can't find any formatting information for this class type it uses the formatting information of the base PS> $blah.pstypenames System.Management.Automation.PSCustomObject System.Object # let's examine the members of PSTypeNames PS> Get-Member -In $blah.pstypenames TypeName: System.Management.Automation.Runspaces.ConsolidatedString Name MemberType Definition ---- ---------- ---------- Add Method void Add(string item), void ICollection[string].Add(string item), int IList.Add(System.Object value) Clear Method void Clear(), void ICollection[string].Clear(), void IList.Clear() Contains Method bool Contains(string item), bool ICollection[string].Contains(string item), bool IList.Contains(System.Object value) CopyTo Method void CopyTo(string[] array, int index), void ICollection[string].CopyTo(string[] array, int arrayIndex), void ICollection.... Equals Method bool Equals(System.Object obj) GetEnumerator Method System.Collections.Generic.IEnumerator[string] GetEnumerator(), System.Collections.Generic.IEnumerator[string] IEnumerable... GetHashCode Method int GetHashCode() GetType Method type GetType() IndexOf Method int IndexOf(string item), int IList[string].IndexOf(string item), int IList.IndexOf(System.Object value) Insert Method void Insert(int index, string item), void IList[string].Insert(int index, string item), void IList.Insert(int index, Syste... Remove Method bool Remove(string item), bool ICollection[string].Remove(string item), void IList.Remove(System.Object value) RemoveAt Method void RemoveAt(int index), void IList[string].RemoveAt(int index), void IList.RemoveAt(int index) ToString Method string ToString() Item ParameterizedProperty string Item(int index) {get;set;} Count Property int Count {get;} IsFixedSize Property bool IsFixedSize {get;} IsReadOnly Property bool IsReadOnly {get;} IsSynchronized Property bool IsSynchronized {get;} SyncRoot Property System.Object SyncRoot {get;} |
The members Clear
and Add
seem to be what we want:
1 2 3 4 5 6 7 8 9 10 11 12 |
# clear the existing types PS> $blah.pstypenames.Clear() # confirm they are cleared PS> $blah.pstypenames # add the new type name PS> $blah.pstypenames.Add("System.MyObject1") # confirm it's set PS> $blah.pstypenames System.MyObject1 |
Instead of clearing the existing types, one can Insert
the new type to the top of the list too:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# here are the initial types PS> $blah.pstypenames System.Management.Automation.PSCustomObject System.Object # insert a new type at index 0 PS> $blah.pstypenames.Insert(0,"System.MyObject1") # notice it's added to the top of the list PS> $blah.pstypenames System.MyObject1 System.Management.Automation.PSCustomObject System.Object |
Gotta love it when things fall into place and you have a language that makes it easy to do all these things!
My thanks to this and this post for pointing me towards PSTypeNames
.